import Plugin, { filterHooks } from './Plugin'
import {
    run as runSubscription,
    unlisten as unlistenSubscription
} from './subscription'
import getReducer from './getReducer'
import prefixNamespace from './prefixNamespace' 
import checkModel from './checkModel'
import createPromiseMiddleware from './createPromiseMiddleware'
import getSaga from './getSaga'
import createStore from './createStore'
import createSagaMiddleware from 'redux-saga/lib/internal/middleware'
import { combineReducers } from 'redux'


// 内部model, 当进行unmodel时更新全局的state, why???
const dvaModel = {
    namespace: '@@dva',
    state: 0,
    reducers: {
        UPDATE (state) {
            return state + 1
        }
    }
}


/**
 * 
 * @param {*} hooksAndOpts  dva(opt)中的opts
 * @param {*} createOpts  dva()中创建的createOpts {initialReducer, setupMiddlewares, setupApp}
 */
export function create (hooksAndOpts = {}, createOpts = {}) {
    const { initialReducer, setupApp = noop } = createOpts

    const plugin = new Plugin()
    plugin.use(filterHooks(hooksAndOpts)) // 从hooksAndOpts中找出hooks并使用这些hooks

    const app = {
        _models: [prefixNamespace({...dvaModel})],  // 设置第一个model，全局的
        _store: null,
        _plugin: plugin,
        use: plugin.use.bind(plugin),
        model,
        start
    }
    return app

    function model (m) {
        if (process.env.NODE_ENV !== 'production') {
            // 开发环境下进行model的格式校验
            checkModel(m, app._models)
        }
        const prefixedModel = prefixNamespace({...m})   // 给reducers, effects加上namepsace前缀
        app._models.push(prefixedModel) // 直接往里push
        return prefixedModel
    }

    // 调用app.start之后进行的app.model操作走这个方法
    function injectModel (createReducer, onError, unlisteners, m) {
        m = model(m)

        const store = app._store    // 这时候已经有store
        store.asyncReducers[m.namespace] = getReducer(  // 异步的reducers
            m.reducers,
            m.state,
            plugin._handleActions
        )

        // 调用redux的store.replacer, 把当前的reducer替换掉
        store.replaceReducer(createReducer(store.asyncReducers))

        // 如果存在effects, 就执行runSaga
        if (m.effects) {
            
            store.runSaga(
                app._getSaga(m.effects, m, onError, plugin.get('onEffect'))
            )
        }

        // 如果存在subscriptions就执行subscription
        if (m.subscriptions) {
            unlisteners[m.namespace] = runSubscription(
                m.subscriptions,
                m,
                app,
                onError
            )
        }

    }

    /**
     * 卸载model
     * @param {*} createReducer 
     * @param {*} reducers 
     * @param {*} unlisteners 
     * @param {*} namespace 
     */
    function unmodel (createReducer, reducers, unlisteners, namespace) {
        const store = app._store

        delete store.asyncReducers[namespace]   // 删除异步reducers
        delete reducers[namespace]  // 删除reducers

        store.replaceReducer(createReducer())   // 重新创建reducer并执行replace
        
        // 取消effects, 目测是触发内部的方法
        store.dispatch({type: `${namespace}/@@CANCEL_EFFECTS`})

        // 卸载subscriptions
        unlistenSubscription(unlisteners, namespace)

        app._models = app._models.filter(model => model.namespace !== namespace)    // 删除相应的model
    }

    /**
     * core.create().start方法，在app.start()中会调用
     */
    function start () {
        // 全局的错误处理
        const onError = err => {
            if (err) {
                if (typeof err === 'string') err = new Error(err)
                err.preventDefault = () => {
                    err._dontReject = true
                }

                // 执行onerror中间件
                plugin.apply('onError', err => {
                    throw new Error(err.stack || err)
                })(err, app._store.dispatch)
            }
        }

        const sagaMiddleware = createSagaMiddleware()
        const promiseMiddleware = createPromiseMiddleware(app)
        app._getSaga = getSaga.bind(null)

        const sagas = []
        const reducers = { ...initialReducer } 

        for (const m of app._models) {
            reducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions)
            if (m.effects) {
                sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect')))
            }
        }

        const reducerEnhancer = plugin.get('onReducer')
        const extraReducers = plugin.get('extraReducers')

        // extraReducers 和其他的reducers冲突了
        invariant(
            Object.keys(extraReducers).every(key => !(key in reducers)),
            `[app.start] extraReducers is conflict with other reducers, reducers list: ${Object.keys(reducers).join(', ')}`
        )

        const store = (app._store = createStore({
            reducers: createReducer(),
            initialState: hooksAndOpts.initialState || {},
            plugin,
            createOpts,
            sagaMiddleware,
            promiseMiddleware
        }))

        store.runSaga = sagaMiddleware.run
        store.asyncReducers = {}    // app.start之后的reducers

        // 监听state改变并执行onStateChange
        const listeners = plugin.get('onStateChange')
        for (const listener of listeners) {
            store.subscribe(() => {
                listener(store.getState())
            })
        }

        // 运行sagas
        sagas.forEach(sagaMiddleware.run)

        // setup
        setupApp(app)

        // 执行model.subscriptions并记录unlisteners
        const unlisteners = {}
        for (const model of this._models) {
            if (model.subscriptions) {
                unlisteners[model.namespace] = runSubscription(
                    model.subscriptions,
                    model,
                    app,
                    onError
                )
            }
        }

        app.model = injectModel.bind(app, createReducer, onError, unlisteners)   // 覆盖app.start之前的app.model方法
        app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners)

        
        function createReducer () {
            return reducerEnhancer(
                combineReducers({   // combine reducers合成一个大的reducers
                    ...reducers,
                    ...extraReducers,
                    ...(app._store ? app._store.asyncReducers : {})
                })
            )
        }
    }
}